package org.epidroid.epidroid.chronos.ui;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import org.epidroid.epidroid.R;
import org.epidroid.epidroid.chronos.engine.Chronos;
import org.epidroid.epidroid.chronos.engine.exceptions.ChronosException;
import org.epidroid.epidroid.chronos.engine.objects.ChronosCourse;
import org.epidroid.epidroid.chronos.engine.objects.ChronosDay;
import org.epidroid.epidroid.chronos.engine.objects.ChronosInstructor;
import org.epidroid.epidroid.chronos.engine.objects.ChronosRoom;
import org.epidroid.epidroid.chronos.engine.objects.ChronosTrainee;
import org.epidroid.epidroid.chronos.engine.objects.ChronosWeek;
import org.epidroid.epidroid.chronos.engine.utils.ChronosUtils;
import org.epidroid.epidroid.chronos.service.ChronosService;
import org.epidroid.epidroid.chronos.service.ChronosServiceBinder;
import org.epidroid.epidroid.chronos.service.IChronosService;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;

/*
 * TODO: handle increment of the number of the week when this is the last week of the year
 * 
 */
public class ChronosViewActivity extends Activity {

	public static int DISPLAY_DAY = 1;
	public static int DISPLAY_WEEK = 2;

	/*
	 * Minimum number of hours that should fit in the whole screen.
	 */
	private static int HOURS_IN_WHOLE_SCREEN = 18;

	private IChronosService _service = null;

	private TableLayout _hoursPane = null;
	private ScrollView _scrollView = null;
	private ArrayList<String> _hoursList = null;
	private LinearLayout _columns = null;
	private int _hourSize = 0;

	private ImageButton _buttonPrev = null;
	private ImageButton _buttonNext = null;
	private TextView _textDesc = null;

	private boolean _isLoading = false;

	private ServiceConnection _serviceConnection = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		bindWithChronosService();
	}

	@Override
	protected void onDestroy() {
		// unbindService(_serviceConnection);
		super.onDestroy();
	}

	private void bindWithChronosService() {
		Intent intent = new Intent(this, ChronosService.class);

		_serviceConnection = new ServiceConnection() {
			public void onServiceConnected(ComponentName name,
					IBinder servicebinder) {
				IChronosService service = ((ChronosServiceBinder) servicebinder)
						.getService();
				_service = service;
				init();
			}

			public void onServiceDisconnected(ComponentName name) {
				Log.i(Chronos.LOGCAT, "Service has disconnected!");
			}
		};

		getApplicationContext().bindService(intent, _serviceConnection,
				Context.BIND_AUTO_CREATE);
	}

	private void init() {
		setContentView(R.layout.chronos_view);

		_scrollView = (ScrollView) findViewById(R.id.chronos_calendar_scrollview);
		_hoursPane = (TableLayout) findViewById(R.id.chronos_hours_pane);
		_columns = (LinearLayout) findViewById(R.id.chronos_calendar_columns);
		_buttonPrev = (ImageButton) findViewById(R.id.chronos_calendar_prev);
		_buttonNext = (ImageButton) findViewById(R.id.chronos_calendar_next);
		_textDesc = (TextView) findViewById(R.id.chronos_calendar_desc);
		buildHours();

		Intent intent = getIntent();
		String group = intent.getStringExtra("group");
		int displayType = intent.getIntExtra("type", DISPLAY_WEEK);

		/*
		 * TODO: add the possibility to configure which day/week is displayed.
		 * Right now we are displaying the current week / day by default.
		 */

		if (displayType == DISPLAY_WEEK)
			switchToWeek(-1, group);
		else if (displayType == DISPLAY_DAY) {
			// Retrive current day
			int weekDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
			switchToDay(-1, weekDay, group);
		}

	}

	private boolean isToday(ChronosDay day) {
		Calendar cal1 = Calendar.getInstance();
		cal1.setTime(day.getDate());
		Calendar cal2 = Calendar.getInstance();
		if (cal1 == null || cal2 == null) {
			return false;
		}
		return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA)
				&& cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1
					.get(Calendar.DAY_OF_YEAR) == cal2
				.get(Calendar.DAY_OF_YEAR));
	}

	private void switchToWeek(final int numWeek, final String group) {
		setIsLoading();
		Runnable run = new Runnable() {
			@Override
			public void run() {
				final ChronosWeek week;
				try {
					week = _service.getWeek(numWeek, group);
				} catch (ChronosException e) {
					Log.e(Chronos.LOGCAT, e.getMessage());
					displayError("Could not find week");
					return;
				}
				ChronosViewActivity.this.runOnUiThread(new Runnable() {
					@Override
					public void run() {
						displayWeek(week, group);
						unsetIsLoading();
					}
				});

			}
		};
		Thread t = new Thread(run);
		t.start();
	}

	private void switchToDay(final int numWeek, final int numDay,
			final String group) {
		System.out.println("Switching to: numWeek = " + numWeek + ", numDay = "
				+ numDay + ", group = " + group);
		setIsLoading();
		Runnable run = new Runnable() {
			@Override
			public void run() {
				final ChronosDay day;
				try {
					day = _service.getDay(numWeek, numDay, group);
				} catch (ChronosException e) {
					Log.e(Chronos.LOGCAT, e.getMessage());
					displayError("Could not retrieve day");
					return;
				}
				ChronosViewActivity.this.runOnUiThread(new Runnable() {
					@Override
					public void run() {
						displayDay(numWeek, day, group);
						unsetIsLoading();
					}
				});

			}
		};
		Thread t = new Thread(run);
		t.start();
	}

	private void displayWeek(ChronosWeek week, final String group) {
		initColumns(7);
		List<ChronosDay> days = week.getDays();

		for (ChronosDay day : days) {
			int numCol = Integer.valueOf(day.getId());
			List<ChronosCourse> courses = day.getCourses();
			computeDivs(courses);
			for (ChronosCourse course : courses) {
				displayCourse(course, numCol);
			}
			if (isToday(day)) {
				highlightColumn(Integer.valueOf(day.getId()));
			}
		}
		setDesc(ChronosUtils.formatDateDigitsNoTime(week.getDate()));
		final int curWeek = Integer.valueOf(week.getId());
		setOnButtonNextClick(new Runnable() {

			@Override
			public void run() {
				switchToWeek(curWeek + 1, group);
			}
		});
		setOnButtonPrevClick(new Runnable() {

			@Override
			public void run() {
				switchToWeek(curWeek - 1, group);
			}
		});

	}

	private void displayDay(final int numWeek, ChronosDay day,
			final String group) {
		initColumns(1);

		List<ChronosCourse> courses = day.getCourses();
		computeDivs(courses);
		for (ChronosCourse course : courses) {
			displayCourse(course, 0);
		}
		setDesc(ChronosUtils.formatDateTextNoTime(day.getDate()));
		final int dayId = Integer.valueOf(day.getId());
		final int weekId = Integer.valueOf(day.getWeek().getId());

		setOnButtonNextClick(new Runnable() {

			@Override
			public void run() {
				if (dayId == 6)
					switchToDay(weekId - 1, 0, group);
				else
					switchToDay(weekId, dayId + 1, group);
			}
		});
		setOnButtonPrevClick(new Runnable() {

			@Override
			public void run() {
				if (dayId == 0)
					switchToDay(weekId - 1, 6, group);
				else
					switchToDay(weekId, dayId - 1, group);
			}
		});

	}

	private void buildHours() {
		// Does not work
		int totalSize = _scrollView.getMeasuredHeight();
		int hourSize = totalSize / HOURS_IN_WHOLE_SCREEN;

		// TODO: make this number depend on the size of the screen
		_hourSize = 30;

		_hoursList = new ArrayList<String>();
		for (int i = 0; i < 24; i++) {
			String desc = i + "h";
			if (i < 10)
				desc = "0" + desc;

			TableRow row = new TableRow(this);
			row.setMinimumHeight(_hourSize);

			TextView txtView = new TextView(this);
			txtView.setText(desc);
			row.addView(txtView);
			_hoursPane.addView(row);
		}
	}

	private void clearColumns() {
		_columns.removeAllViews();
	}

	private void initColumns(int nbCol) {
		clearColumns();
		float weight = (float) 1 / (float) nbCol;

		for (int i = 0; i < nbCol; i++) {
			LayoutInflater inflater = (LayoutInflater) this
					.getSystemService(this.LAYOUT_INFLATER_SERVICE);
			RelativeLayout column = (RelativeLayout) inflater.inflate(
					R.layout.chronos_calendar_column, _columns, false);

			if (i % 2 == 1) {
				column.setBackgroundResource(R.drawable.chronos_odd_column_bg);
			}

			LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
					LinearLayout.LayoutParams.FILL_PARENT,
					LinearLayout.LayoutParams.FILL_PARENT);
			params.weight = weight;
			_columns.addView(column, params);
		}
	}

	private void highlightColumn(int nbCol) {
		RelativeLayout column = (RelativeLayout) _columns.getChildAt(nbCol);
		if (null == column)
			return;
		column.setBackgroundResource(R.drawable.chronos_hl_column_bg);
	}

	private void displayCourse(ChronosCourse course, int col) {
		float start = Float.valueOf(course.getHour()) / (float) 4;
		float duration = Float.valueOf(course.getDuration()) / (float) 4;
		displayCourse(col, start, duration, course);
	}

	private void displayCourse(int col, float start, float duration,
			ChronosCourse course) {

		// Retrieve the RelativeLayout that corresponds to the given column
		View v = _columns.getChildAt(col);
		if (!(v instanceof RelativeLayout)) {
			Log.e(Chronos.LOGCAT, "View is not of type RelativeLayout");
			return;
		}

		int yPos = (int) Math.floor(start * _hourSize);
		int height = (int) Math.floor((duration) * _hourSize);

		RelativeLayout layout = (RelativeLayout) v;

		LayoutInflater inflater = (LayoutInflater) this
				.getSystemService(this.LAYOUT_INFLATER_SERVICE);

		LinearLayout buttonLayout = (LinearLayout) inflater.inflate(
				R.layout.chronos_event, layout, false);
		Button txt = (Button) buttonLayout
				.findViewById(R.id.chronos_event_button);

		addButtonClickListener(txt, course);
		txt.setText(course.getTitle());

		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
				RelativeLayout.LayoutParams.FILL_PARENT, height);
		params.topMargin = yPos;

		LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(0,
				LinearLayout.LayoutParams.FILL_PARENT);
		if (course.getDisplayDiv() == 0)
			params2.weight = (float) 1;
		else {
			int tmp = 1;
			
			ChronosCourse c = course.getDisplayOverlappingCourse();
			if ((c != null) && (c.getDisplayDiv() == course.getDisplayDiv()))
				tmp++;

			params2.weight = (float) 1 / (float) (tmp + course.getDisplayDiv());
		}
		txt.setLayoutParams(params2);

		layout.addView(buttonLayout, params);
	}

	private void addButtonClickListener(Button button,
			final ChronosCourse course) {
		button.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				displayCourse(course);
			}
		});
	}

	/*
	 * TODO: retrieve the list of courses that share a same timespace and adjust
	 * the position and size accordingly.
	 */
	private void displayCourse(ChronosCourse course) {
		AlertDialog.Builder alert = new AlertDialog.Builder(this);
		alert.setTitle(course.getTitle());
		String desc = "Start: " + course.getHour() + "\n" + "Duration: "
				+ course.getDuration() + "\n";
		desc += "Instructors:\n";
		for (ChronosInstructor i : course.getInstructors()) {
			desc += "- " + i.getName() + "\n";
		}
		desc += "Rooms:\n";
		for (ChronosRoom r : course.getRooms()) {
			desc += "- " + r.getName() + "\n";
		}
		desc += "Trainees:\n";
		for (ChronosTrainee t : course.getTrainees()) {
			desc += "- " + t.getName() + "\n";
		}
		alert.setMessage(desc);
		alert.setPositiveButton("Close", null);
		alert.show();
	}

	private void setIsLoading() {
		// TODO: display image instead?
		_textDesc.setText("Loading...");
		_isLoading = true;
	}

	private void unsetIsLoading() {
		_isLoading = false;
	}

	private void setDesc(String desc) {
		_textDesc.setText(desc);
	}

	private void setOnButtonPrevClick(final Runnable run) {
		_buttonPrev.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				if (_isLoading)
					return;
				run.run();
			}
		});
	}

	/*
	 * TODO: make sure the buttons are blocked while performing
	 */
	private void setOnButtonNextClick(final Runnable run) {
		_buttonNext.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				if (_isLoading)
					return;
				run.run();
			}
		});
	}

	private void displayError(String error) {
		// TODO: display error popup
	}

	/*
	 * Computes the offset of the events, so that overlapped events can be
	 * properly displayed.
	 */
	private void computeDivs(List<ChronosCourse> courses) {

		for (ChronosCourse c1 : courses) {
			c1.setDisplayDiv(0);
			// Log.d(Chronos.LOGCAT, c1.getTitle() + " -> " + c1.getHour() +
			// ", "
			// + c1.getDuration());
		}

		for (int i = 0; i < courses.size() - 1; i++) {
			ChronosCourse c1 = courses.get(i);

			for (int j = i + 1; j < courses.size(); j++) {
				ChronosCourse c2 = courses.get(j);

				Float c1_start = Float.valueOf(c1.getHour());
				Float c2_start = Float.valueOf(c2.getHour());
				Float c1_duration = Float.valueOf(c1.getDuration());

				if ((c1_start + c1_duration) > (c2_start)) {
					c2.setDisplayDiv(c2.getDisplayDiv() + 1);
					c2.setDisplayOverlappingCourse(c1);
				}
			}
		}

		for (ChronosCourse c1 : courses) {
			Log.d(Chronos.LOGCAT, c1.getTitle() + " -> " + c1.getDisplayDiv());
		}
	}
}
